﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://wpfimageviewer.codeplex.com
using System.Collections.Generic;
using System.Threading;
using System.Linq;
using BrainTechLLC;
using BrainTechLLC.ThreadSafeObjects;
using System;
using System.Diagnostics;

namespace ImageAppDataModels
{
    public class PreLoader<T> where T : class
    {
        protected Random _rand = new Random((int)(DateTime.Now.Ticks % (long)(int.MaxValue - 1)));
        protected int _currentGeneration;
        public ThreadSafeQueue<PreloadEntry<T>> _preloadQueue = new ThreadSafeQueue<PreloadEntry<T>>();
        protected object _lockObject = new object();
        protected bool _initialized;

        protected List<Thread> _preloadThreads = new List<Thread>();
        protected List<ThreadStart> _preloads = new List<ThreadStart>();
        public ThreadSafeList<ICanBePreloaded<T>> _working = new ThreadSafeList<ICanBePreloaded<T>>();
        protected bool _stopped;
        protected bool _wantExit;
        protected int _numberOfThreads;
        protected ThreadPriority _preloadPriority;
        protected bool _reEnqueueExceptions;

        public int GetWorkingQueueSize()
        {
            return _working.Count;
        }

        public int GetPreloadQueueSize()
        {
            return _preloadQueue.Count;
        }

        protected FastSmartWeakEvent<EventHandler<ObjectEventArgs<ICanBePreloaded<T>>>> _preloadComplete = new FastSmartWeakEvent<EventHandler<ObjectEventArgs<ICanBePreloaded<T>>>>();
        public event EventHandler<ObjectEventArgs<ICanBePreloaded<T>>> PreloadComplete { add { _preloadComplete.Add(value); } remove { _preloadComplete.Remove(value); } }

        public PreLoader(int numberOfThreads, ThreadPriority preloadPriority, bool reEnqueueExceptions)
        {
            _stopped = true;
            _numberOfThreads = numberOfThreads;
            _preloadPriority = preloadPriority;
            _reEnqueueExceptions = reEnqueueExceptions;
        }

        public void Stop()
        {
            _stopped = true;
        }

        public void Exit()
        {
            _wantExit = true;
        }

        public void Resume()
        {
            _stopped = false;
        }

        public bool IsInList(ICanBePreloaded<T> item)
        {
            if (_working.Contains(item))
                return true;

            return _preloadQueue.IsInList((preload) => preload._item == item);
        }

        public bool EnqueueIfNotInList(ICanBePreloaded<T> item)
        {
            CheckInit();

            if (!IsInList(item))
            {
                AddPreload(item);
                return true;
            }

            return false;
        }

        public void Clear()
        {
            _preloadQueue.Clear();
        }

        public void Start()
        {
            _stopped = false;

            if (_numberOfThreads < 1)
                _numberOfThreads = 1;

            if (_preloads.Count > 0)
                return;

            for (int n = 0; n < _numberOfThreads; n++)
            {
                _preloads.Add(new ThreadStart(HandlePreload));
            }

            for (int n = 0; n < _numberOfThreads; n++)
            {
                Thread t = _preloads[n].CreateAndRunThread(true);
                t.Name = "Preloader " + typeof(T).Name;
                _preloadThreads.Add(t);
                Thread.Sleep(12);
            }

            for (int n = 0; n < _numberOfThreads; n++)
            {
                _preloadThreads[n].Priority = _preloadPriority;
            }
        }

        protected void CheckInit()
        {
            if (!_initialized)
            {
                lock (_lockObject)
                {
                    if (!_initialized)
                    {
                        Start();
                        _initialized = true;
                    }
                }
            }
        }

        protected void AddPreload(ICanBePreloaded<T> item)
        {
            if (_preloadQueue.IsInList((i) => i._item == item) || _working.Contains(item))
            {
                return;
            }

            var preload = new PreloadEntry<T>(item, _currentGeneration);
            _preloadQueue.Enqueue(preload);
        }

        public void AddPreloads(List<ICanBePreloaded<T>> items)
        {
            CheckInit();

            lock (_lockObject)
            {
                _currentGeneration++;

                //var v = from item in items.AsParallel()
                //        select item;

                //v.ForAll(item => AddPreload(item));
                foreach (var item in items)
                {
                    AddPreload(item);
                }
            }
        }

        public void HandlePreload()
        {
            while (true)
            {
                if (_wantExit)
                    return;

                while (_preloadQueue.Count == 0 || _stopped)
                {
                    if (_wantExit)
                        return;

                    Thread.Sleep(20);
                }

                PreloadEntry<T> preload;

                while (_preloadQueue.Dequeue(out preload))
                {
                    // Console.WriteLine("Preloading: " + preload._item.ToString());

                    if (_wantExit)
                        return;

                    if (_currentGeneration != preload._generation)
                    {
                        preload._item = null;

                        continue;
                    }

                    if (preload._item == null)
                    {
                        continue;
                    }

                    _working.Add(preload._item);

                    try
                    {
                        if (preload.IsAlreadyLoaded())
                        {
                            _working.Remove(preload._item);
                            continue;
                        }

                        preload.Preload();
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex.ToString());
                        Debug.Assert(false);
                        Console.WriteLine(ex.ToString());

                        if (_reEnqueueExceptions)
                        {
                            if (preload._item != null)
                            {
                                Debug.WriteLine("Re-enqueing " + preload._item.ToString());
                                _preloadQueue.Enqueue(preload);
                            }
                        }
                    }
                    finally
                    {
                        if (preload._item != null)
                        {
                            _working.Remove(preload._item);
                        }

                        // may want to pass in preload._item
                        _preloadComplete.Raise(this, new ObjectEventArgs<ICanBePreloaded<T>>(preload._item));
                        preload._item = null;
                        
                        if (preload._item != null)
                        {
                            _working.Remove(preload._item);
                        }

                        // may want to pass in preload._item
                        _preloadComplete.Raise(this, new ObjectEventArgs<ICanBePreloaded<T>>(preload._item));
                        preload._item = null;
                        preload = null;
                    }
                    // Thread.Sleep(_rand.Next(2, 6)); // was 18
                }
            }
        }
    }
}

